# -*- coding: utf8 -*-
"""
Hopf algebra : Word Quasisymetric functions.

AUTHOR:

- Jean-Baptiste Priez
"""
#*****************************************************************************
#       Copyright (C) 2012 Jean-Baptiste Priez <jbp@kerios.fr>,
#
#  Distributed under the terms of the GNU General Public License (GPL)
#
#    This code is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    General Public License for more details.
#
#  The full text of the GPL is available at:
#
#                  http://www.gnu.org/licenses/
#*****************************************************************************
from good_hopf_algebra import GoodHopfAlgebras
from pattern import FundamentalBasis, FundamentalDualBasis, \
    getitem_for_wordlike_indices
from sage.categories.graded_hopf_algebras_with_basis import \
    GradedHopfAlgebrasWithBasis
from sage.categories.realizations import Category_realization_of_parent
from sage.combinat.free_module import CombinatorialFreeModule
from sage.misc.bindable_class import BindableClass
from sage.structure.parent import Parent
from sage.structure.unique_representation import UniqueRepresentation

def pw_latex_term( self, m ):
    from sage.combinat.free_module import CombinatorialFreeModule
    if len( m ) == 0 :
        return '1'
    else: return CombinatorialFreeModule._latex_term( self, m )

class WordQuasisymmetricFunctions( UniqueRepresentation, Parent ):

    def __init__( self, R ):
        from sage.categories.all import Rings
        assert( R in Rings() )
        self._base = R
        Parent.__init__( self, category = GoodHopfAlgebras( R ).WithRealizations() )

    def phi( self, indice ):
        '''
        TESTS::
        
            sage: WQSym(QQ).phi([2,5,21])
            [1, 2, 3]
            sage: WQSym(QQ).phi([2,5,5])
            [1, 2, 2]
        '''
        from packed_words import to_pack
        return to_pack( indice )

    def shift( self, pw1, pw2 ):
        '''
        EXAMPLES::
        
            sage: WQSym(QQ).shift([1,1],[1,2,2])
            [2, 3, 3]
        '''
        return [i + max( list( pw1 ) + [0] ) for i in pw2]

    def indices( self ):
        '''
        EXAMPLES::
        
            sage: WQSym(QQ).indices()
            Packed words
        '''
        from packed_words import PackedWords
        return PackedWords()

    def _repr_( self ):
        r"""
        EXAMPLES::

            sage: WordQuasisymmetricFunctions(QQ)
            Hopf algebra of Word Quasi-Symmetric Functions over the Rational Field
        """
        return "Hopf algebra of Word Quasi-Symmetric Functions over the %s" % self.base_ring()

    def a_realization( self ):
        return self.M()

    def dual( self ):
        return self

    def __init_extra__( self ):
        '''
        TESTS::

            sage: S = WQSym(QQ).S();Sh = WQSym(QQ).Sh();Se = WQSym(QQ).Se()
            sage: Sh(S[2,2,1,1])
            -Sh[2, 1, 2, 1] + Sh[2, 2, 1, 1]
            sage: S(Sh[1,1,2])
            S[1, 1, 2]
            sage: S(Se[1,1,2])
            S[1, 1, 2] + S[1, 2, 1] + S[2, 1, 1]
            sage: s = S(Se[1,1,2] * Se[1,1]);s
            S[1, 1, 2, 3, 3] + S[1, 1, 3, 2, 3] + S[1, 1, 3, 3, 2] + S[1, 2, 1, 3, 3] + S[1, 2, 3, 1, 3] + S[1, 2, 3, 3, 1] + S[1, 3, 1, 2, 3] + S[1, 3, 1, 3, 2] + S[1, 3, 2, 1, 3] + S[1, 3, 2, 3, 1] + S[1, 3, 3, 1, 2] + S[1, 3, 3, 2, 1] + S[2, 1, 1, 3, 3] + S[2, 1, 3, 1, 3] + S[2, 1, 3, 3, 1] + S[2, 3, 1, 1, 3] + S[2, 3, 1, 3, 1] + S[2, 3, 3, 1, 1] + S[3, 1, 1, 2, 3] + S[3, 1, 1, 3, 2] + S[3, 1, 2, 1, 3] + S[3, 1, 2, 3, 1] + S[3, 1, 3, 1, 2] + S[3, 1, 3, 2, 1] + S[3, 2, 1, 1, 3] + S[3, 2, 1, 3, 1] + S[3, 2, 3, 1, 1] + S[3, 3, 1, 1, 2] + S[3, 3, 1, 2, 1] + S[3, 3, 2, 1, 1]
            sage: Se(s)
            Se[1, 1, 2, 3, 3]
            sage: s = S(Sh[1,1,2] * Sh[1,1]); s
            S[1, 1, 2, 3, 3] + S[1, 1, 3, 2, 3] + S[1, 1, 3, 3, 2] + S[1, 3, 1, 2, 3] + S[1, 3, 1, 3, 2] + S[1, 3, 3, 1, 2] + S[3, 1, 1, 2, 3] + S[3, 1, 1, 3, 2] + S[3, 1, 3, 1, 2] + S[3, 3, 1, 1, 2]
            sage: Sh(s)
            Sh[3, 3, 1, 1, 2]
        '''
        S = self.S(); M = self.M(); Me = self.Me(); Ms = self.Ms()

        morph = lambda F, T, func, tri = None, comp = None: ( 
            F._module_morphism( func, codomain = T, triangular = tri, cmp = comp )
            if comp is not None else
            F._module_morphism( func, codomain = T, triangular = tri ) )

        # M <-> Me
        from packed_words import quasi_cmp
        Me_to_M = morph( Me, M, M.sum_of_fatter,
            tri = "lower", comp = quasi_cmp )
        Me_to_M.register_as_coercion()
        ( ~Me_to_M ).register_as_coercion()
        # M <-> Ms 
        Ms_to_M = morph( Ms, M, M.sum_of_finer,
            tri = "upper", comp = quasi_cmp )
        Ms_to_M.register_as_coercion()
        ( ~Ms_to_M ).register_as_coercion()
        # Ms <-> Me 
        morph( Me, Ms, ( ~Ms_to_M ) * Me_to_M ).register_as_coercion()
        morph( Ms, Me, ( ~Ms_to_M ) * Ms_to_M ).register_as_coercion()


        ### ---- projection from QSym ---- ###
#        from sage.combinat.ncsf_qsym.qsym import QuasiSymmetricFunctions
#        m = QuasiSymmetricFunctions( self.base() ).Monomial()
#        # m_to_M 
#        def Composition_to_PackedWords( comp ):
#            from sage.combinat.words.shuffle_product import ShuffleProduct_w1w2
#            from sage.combinat.words.word import Word
#            i = 1;l = []
#            for v in comp:
#                l.append( [i for _ in range( v )] )
#                i += 1
#            shuff = [[]]
#            for monomi in l:
#                tmp = []
#                for w in shuff:
#                    tmp += list( ShuffleProduct_w1w2( Word( w ), Word( monomi ) ) )
#                shuff = tmp
#            return [self.indices()( w ) for w in shuff]
#
#        morph( m, M, lambda compo: M.sum_of_monomials( Composition_to_PackedWords( compo ) ) ).register_as_coercion()

    _shorthands = set( ['S', 'M', 'Me', 'Ms'] )

    class Bases( Category_realization_of_parent ):

        def super_categories( self ):
            R = self.base().base_ring()
            return [GoodHopfAlgebras( R ).Realizations(),
                    GradedHopfAlgebrasWithBasis( R ).Realizations()]

        class ParentMethods:

            def sum_of_finer( self, perm ):
                return self.sum_of_monomials( perm.quasi_permutohedron_smaller() )

            def sum_of_fatter( self, perm ):
                return self.sum_of_monomials( perm.quasi_permutohedron_greater() )

    class Fundamental( BindableClass, FundamentalBasis ):
        def __init__( self, FQSym ):
            FundamentalBasis.__init__( self, FQSym )
            self.print_options( prefix = "S",
                    latex_prefix = "\\mathbb{S}",
                    bracket = False )

        _latex_term = pw_latex_term

    S = Fundamental

    class FundamentalDual( BindableClass, FundamentalDualBasis ):
        def __init__( self, WQSym ):
            '''
                .. todo::
            '''
            FundamentalDualBasis.__init__( self, WQSym )
            self.print_options( prefix = "M",
                    latex_prefix = "\\mathbb{M}",
                    bracket = False )

        _latex_term = pw_latex_term

        def product_on_basis( self, pw1, pw2 ):
            from quasi_shuffle_product import Shifted_QuasiShuffleProduct
            from packed_words import ordered_partition_sets_to_packed_word
            return self.sum_of_monomials( 
                ordered_partition_sets_to_packed_word( osp )
                for osp in Shifted_QuasiShuffleProduct( 
                        pw1.to_ordered_partition_sets(),
                        pw2.to_ordered_partition_sets() ) )

    M = FundamentalDual

    class Elementary( BindableClass, CombinatorialFreeModule ):

        def __init__( self, FQSym ):
            r"""
            """
            CombinatorialFreeModule.__init__( 
                self, FQSym.base_ring(), FQSym.indices(),
                category = FQSym.Bases(),
                prefix = "Me", latex_prefix = "\\mathcal{E}",
                    bracket = False )

        from pattern import getitem_for_wordlike_indices
        __getitem__ = getitem_for_wordlike_indices
        _latex_term = pw_latex_term

        def product_on_basis( self, e1, e2 ):
            '''
                .. todo:: 
            '''
            if len( e1 ) == 0: return self.monomial( e2 )
            return self.monomial( 
                self.indices()( list( e1 ) + [i + max( e1 ) for i in e2] ) )

    Me = Elementary

    class Homogene( BindableClass, CombinatorialFreeModule ):

        def __init__( self, FQSym ):
            r"""
            
            .. todo:: make comment and test
            """
            CombinatorialFreeModule.__init__( 
                self, FQSym.base_ring(), FQSym.indices(),
                category = FQSym.Bases(),
                prefix = "Ms", latex_prefix = "\\mathcal{S}",
                    bracket = False )

        from pattern import getitem_for_wordlike_indices
        __getitem__ = getitem_for_wordlike_indices
        _latex_term = pw_latex_term

        def product_on_basis( self, e1, e2 ):
            if len( e2 ) == 0:
                return self.monomial( e1 )
            return self.monomial( 
                self.indices()( [i + max( e2 ) for i in e1] + list( e2 ) ) )

    Ms = Homogene


